package app

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"

	"gitee.com/extrame/fb-runner/store"
	"gitee.com/extrame/fb-runner/util"
	"github.com/boltdb/bolt"
	"github.com/sirupsen/logrus"
)

type Chaincode struct {
	PackageId    string `json:"package_id"`
	Label        string `json:"label"`
	Approved     bool
	Commited     bool
	Version      uint64
	VersionLabel string
	Channel      string
	Name         string
	InstalledAt  time.Time
	Historic     bool
	References   map[string]Channel `json:"references"`
}

type Channel struct {
	Chaincodes []ChaincodeInChannel `json:"chaincodes"`
}

type ChaincodeInChannel struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

func findVersion(ver string) string {
	numR := regexp.MustCompile(`\d+`)
	matched := numR.FindAllStringSubmatchIndex(ver, -1)
	if len(matched) > 0 {
		var last = matched[len(matched)-1]
		var tiny = ver[last[0]:last[1]]
		tiny_num, _ := strconv.ParseUint(tiny, 10, 64)
		var newTiny = strconv.FormatUint(tiny_num+1, 10)
		if last[0] == 0 {
			if last[1] == len(ver) {
				return newTiny
			}
			return newTiny + ver[last[1]:]
		} else {
			if last[1] == len(ver) {
				return ver[:last[0]] + newTiny
			}
			return ver[:last[0]] + newTiny + ver[last[1]:]
		}
	}
	return ""
}

func (c *Chaincode) GenerateVersionLabel() string {
	var newLabel = findVersion(c.VersionLabel)
	if newLabel == "" {
		return "v1.0.0"
	}
	return newLabel
}

func GetInstalledChaincodes() map[string]*Chaincode {
	var codes = make(map[string]*Chaincode)
	store.DB.View(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b = tx.Bucket([]byte("chaincode")); b != nil {
			return b.ForEach(func(k, v []byte) error {
				var c Chaincode
				json.Unmarshal(v, &c)
				logrus.Info("从DB中获得代码：", c)
				codes[string(k)] = &c
				return nil
			})
		} else {
			logrus.Infoln("chaincode暂不存在，请运行sync_installed同步状态")
		}
		return
	})
	return codes
}

func GetInstalledChaincode(pid string) *Chaincode {
	var codes *Chaincode
	store.DB.View(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b = tx.Bucket([]byte("chaincode")); b != nil {
			bts := b.Get([]byte(pid))
			if bts != nil {
				codes = new(Chaincode)
				json.Unmarshal(bts, codes)
			}
		} else {
			logrus.Infoln("chaincode暂不存在，请运行sync_installed同步状态")
		}
		return
	})
	return codes
}

func GetInstalledChaincodeByName(name string) []*Chaincode {
	var codes []*Chaincode
	store.DB.View(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b = tx.Bucket([]byte("chaincode")); b != nil {
			b.ForEach(func(k, bts []byte) error {
				code := new(Chaincode)
				json.Unmarshal(bts, code)
				if code.Name == name && !code.Historic {
					codes = append(codes, code)
				}
				return nil
			})
		} else {
			logrus.Infoln("chaincode暂不存在，请运行sync_installed同步状态")
		}
		return
	})
	return codes
}

func CheckNewInstall(result []*Chaincode) []*Chaincode {
	var inDb = GetInstalledChaincodes()
	var newCode []*Chaincode
	var updatedCode []*Chaincode
	var labels = make(map[string]*Chaincode)
	for _, searched := range inDb {
		if searched.Historic {
			continue
		}
		label := strings.SplitN(searched.PackageId, ":", 2)[0]
		if existed, ok := labels[label]; ok {

			//数据库中有
			if (existed.Commited && !searched.Commited) || (existed.Approved && !searched.Approved) {
				updatedCode = append(updatedCode, searched)
				continue
			} else if (searched.Commited && !existed.Commited) || (searched.Approved && !existed.Approved) {
				updatedCode = append(updatedCode, existed)
			} else {
				logrus.Fatalln("[Step.1]数据库中有类似的package，请手工Approve其中一个", existed, searched)
			}
		}
		labels[label] = searched
	}
	for _, code := range result {
		if _, ok := inDb[code.PackageId]; ok {
			logrus.Info(code.PackageId, "已存在，跳过")
			continue
		}
		if existed, ok := labels[code.Label]; ok {
			//数据库中有
			if code.PackageId != existed.PackageId {
				if (existed.Commited && !code.Commited) || (existed.Approved && !code.Approved) {
					updatedCode = append(updatedCode, code)
					continue
				} else {
					logrus.Fatalln("[Step.2]数据库中有类似的package，请手工Approve其中一个", existed, code)
				}
			}
		}
		if _, has := inDb[code.PackageId]; !has {
			code.InstalledAt = time.Now()
			newCode = append(newCode, code)
		}
	}
	for _, p := range updatedCode {
		logrus.Infoln("处理" + p.PackageId)
		var previous = labels[p.Label]
		if util.AskForConfirmation(p.PackageId + "在数据库中有上一版本：" + fmt.Sprintf("[%s]%s", previous.VersionLabel, previous.PackageId) + ",是否将其作为更新版本？") {
			p.Channel = previous.Channel
			p.VersionLabel = previous.VersionLabel
			p.Version = previous.Version
			p.Name = previous.Name
			p.InstalledAt = time.Now()
			p.Approved = false
			p.Commited = false
			p.Historic = false
			previous.Historic = true
			UpdateChaincode(previous)
			AddChaincode(p)
		} else if util.AskForConfirmation(fmt.Sprintf("是否将%s标记为历史版本", p.PackageId)) {
			p.Historic = true
			UpdateChaincode(p)
		} else {
			DeleteChaincode(previous)
		}
	}
	if err := AddChaincode(newCode...); err != nil {
		logrus.Fatal(err)
	}
	return newCode
}

func DeleteChaincode(toDel ...*Chaincode) error {
	return store.DB.Update(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b, err = tx.CreateBucketIfNotExists([]byte("chaincode")); err == nil {
			for _, code := range toDel {
				err := b.Delete([]byte(code.PackageId))
				logrus.Infoln("删除代码", code.PackageId)
				if err != nil {
					return err
				}
			}
		}
		return
	})
}

func AddChaincode(newCode ...*Chaincode) error {
	return store.DB.Update(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b, err = tx.CreateBucketIfNotExists([]byte("chaincode")); err == nil {
			for _, code := range newCode {
				bts, e := json.Marshal(code)
				err = b.Put([]byte(code.PackageId), bts)
				logrus.Infoln("增加代码", string(bts), e, err)
				if err != nil {
					return err
				}
			}
		}
		return
	})
}

func UpdateChaincode(newCode *Chaincode) {
	store.DB.Update(func(tx *bolt.Tx) (err error) {
		var b *bolt.Bucket
		if b, err = tx.CreateBucketIfNotExists([]byte("chaincode")); err == nil {
			bts, _ := json.Marshal(newCode)
			err = b.Put([]byte(newCode.PackageId), bts)
			if err != nil {
				return err
			}
		}
		return
	})
}
